package cron

import (
	"cloudops/src/config"
	"cloudops/src/models"
	"context"
	"fmt"
	"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
	"github.com/gammazero/workerpool"
	"github.com/google/uuid"
	"go.uber.org/zap"
	"math/rand"
	"sync"
	"time"
)

// 同步云上的ecs
func (cm *CronManager) RunSyncCloudResourceEcs(ctx context.Context) {
	start := time.Now()
	// 首先应该判断 上次的结果
	if !cm.GetEcsSynced() {
		cm.Sc.Logger.Info("ecs上次同步还未完成，本次cron跳过")
		return
	}
	// 上次完成了，就开始新的了
	cm.SetEcsSynced(false)
	// 获取本地的uid对应的hashmap
	dbUidHashM, err := models.GetResourceEcsUidAndHash()
	if err != nil {
		cm.Sc.Logger.Error("ecs 获取本地的uid对应的hashM", zap.Error(err))
		return
	}
	// 多个账号 先去 公有云同步 所有ecs
	allEsc := sync.Map{}
	wp := workerpool.New(5)
	for _, alic := range cm.Sc.PublicCloudSyncC.AliCloud {
		alic := alic
		wp.Submit(func() {
			// 启动任务
			cm.RunSyncOneCloudEcsAli(alic, &allEsc)
		})
	}
	// TODO 华为 aws的写在这里
	wp.StopWait()
	// 汇总结果 和db中进行增量对比
	// 计算 to_add to_del to_mod
	toAddSet := make([]*models.ResourceEcs, 0)
	toModSet := make([]*models.ResourceEcs, 0)
	toDelUids := []string{}
	localUidSet := make(map[string]struct{})
	var toAddNum, toModNum, toDelNum int
	var suAddNum, suModNum, suDelNum int

	// 遍历远端
	rangeFunc := func(k, v interface{}) bool {
		// uid
		uid := k.(string)
		ecsObj := v.(*models.ResourceEcs)
		localUidSet[uid] = struct{}{}
		dbHash, ok := dbUidHashM[uid]
		if !ok {
			// 在公有云 不在本地数据库
			toAddSet = append(toAddSet, ecsObj)
			toAddNum++
		} else {
			// 在的话对比hash
			if dbHash != ecsObj.Hash {
				toModSet = append(toModSet, ecsObj)
				toModNum++
			}
		}
		return true
	}
	allEsc.Range(rangeFunc)

	// 再遍历 本地
	for uid := range dbUidHashM {
		_, ok := localUidSet[uid]
		if !ok {
			// 说明 不在 公有云，但在 本地
			toDelUids = append(toDelUids, uid)
			toDelNum++
		}
	}

	// 下面开始执行同步 新增
	for _, obj := range toAddSet {
		err := obj.CreateOne()
		if err != nil {
			cm.Sc.Logger.Error("ecs 新增错误", zap.Error(err),
				zap.Any("id", obj.InstanceId),
				zap.Any("name", obj.InstanceName),
			)
			continue
		}
		cm.Sc.Logger.Debug("ecs 新增成功",
			zap.Any("id", obj.InstanceId),
			zap.Any("name", obj.InstanceName),
		)
		suAddNum++
	}

	// 更新
	for _, obj := range toModSet {
		err := obj.UpdateOne()
		if err != nil {
			cm.Sc.Logger.Error("ecs 更新错误", zap.Error(err),
				zap.Any("id", obj.InstanceId),
				zap.Any("name", obj.InstanceName),
			)
			continue
		}
		cm.Sc.Logger.Debug("ecs 更新成功",
			zap.Any("id", obj.InstanceId),
			zap.Any("name", obj.InstanceName),
		)
		suModNum++
	}

	// 删除
	for _, uid := range toDelUids {
		// 先找到这个对象
		dbOjb, err := models.GetResourceEcsByInstanceId(uid)
		if err != nil {
			cm.Sc.Logger.Error("ecs 删除前查找错误", zap.Error(err),
				zap.Any("uid", uid),
			)
			continue
		}
		err = dbOjb.DeleteOne()
		if err != nil {
			cm.Sc.Logger.Error("ecs 删除错误", zap.Error(err),
				zap.Any("uid", uid),
			)
			continue
		}
		cm.Sc.Logger.Info("ecs 删除成功",
			zap.Any("uid", uid),
		)
		suDelNum++
	}

	// 统计耗时
	tookSeconds := time.Since(start).Seconds()
	cm.Sc.Logger.Info("同步ecs结果打印",
		zap.Any("公有云总数", len(localUidSet)),
		zap.Any("数据库", len(dbUidHashM)),
		zap.Any("toAddNum", toAddNum),
		zap.Any("toModNum", toModNum),
		zap.Any("toDelNum", toDelNum),
		zap.Any("suAddNum", suAddNum),
		zap.Any("suModNum", suModNum),
		zap.Any("suDelNum", suDelNum),
		zap.Any("timeTook", tookSeconds),
	)
	cm.SetEcsSynced(true)
}

// 同步阿里云ESC
func (cm *CronManager) RunSyncOneCloudEcsAli(alic *config.AliCloud, allMap *sync.Map) {

	cm.Sc.Logger.Info("ecs同步阿里云开始",
		zap.Any("地区", alic.RegionId),
		zap.Any("账号", alic.AccessKeyId),
		zap.Any("密码", alic.AccessKeySecret),
	)
	// 这里直接mock返回
	rand.Seed(time.Now().UnixNano())
	allnum := MockEcsDescribeEcsResponse(allMap, cm.Sc.PublicCloudSyncC.MockResourceFixedNum, cm.Sc.PublicCloudSyncC.MockResourceRandNum)
	cm.Sc.Logger.Info("ecs同步阿里云结束",
		zap.Any("总数", allnum),
		zap.Any("地区", alic.RegionId),
		zap.Any("账号", alic.AccessKeyId),
		zap.Any("密码", alic.AccessKeySecret),
	)
	return

	// 新建ecs client
	client, err := ecs.NewClientWithAccessKey(
		alic.RegionId,
		alic.AccessKeyId,
		alic.AccessKeySecret,
	)
	if err != nil {
		cm.Sc.Logger.Error("初始化 ecs client错误",
			zap.Error(err),
			zap.Any("RegionId", alic.RegionId),
			zap.Any("AccessKeyId", alic.AccessKeyId),
			zap.Any("AccessKeySecret", alic.AccessKeySecret),
		)
		return
	}
	cm.Sc.Logger.Info("初始化 ecs client成功 ", zap.Any("client", client.SourceIp))
	//TODO
	//resp, err := client.DescribeInstances(&ecs.DescribeInstancesRequest{})
	//if err != nil {
	//	cm.Sc.Logger.Error("ecs DescribeInstances错误",
	//		zap.Error(err),
	//		zap.Any("RegionId", alic.RegionId),
	//		zap.Any("AccessKeyId", alic.AccessKeyId),
	//		zap.Any("AccessKeySecret", alic.AccessKeySecret),
	//	)
	//	return
	//}
	resp := &ecs.DescribeInstancesResponse{}

	cm.Sc.Logger.Info("ecs DescribeInstances 数量",
		zap.Int("ecs", resp.TotalCount),
		zap.Any("RegionId", alic.RegionId),
		zap.Any("AccessKeyId", alic.AccessKeyId),
		zap.Any("AccessKeySecret", alic.AccessKeySecret),
	)

	cloudIns := resp.Instances
	for _, ins := range cloudIns.Instance {
		// 做 sdk中的ecs 结构转化为 db中的
		dbIns := cm.ConvertEcsCloudAli(ins)
		allMap.Store(dbIns.InstanceId, dbIns)
		// 做 hash比较
	}
	return
}

func MockEcsDescribeEcsResponse(allMap *sync.Map, fixedNum, randNum int) int {
	// fixedNum 代表固定的
	// randNum 代表随机的
	rand.Seed(time.Now().UnixNano())

	// 每次host数量 10-30个
	frNum := func(num int64) int {
		if num <= 20 {
			num = 20
		}
		rand.Seed(time.Now().UnixNano())
		return int(rand.Int63n(50) + num)
	}
	all := 0
	// 每次固定的实例，它的uid 要一致
	for i := 0; i < fixedNum; i++ {
		objOne := MockEcsDescribeEcsOne(fmt.Sprintf("固定主机名-%v", i), fmt.Sprintf("fix-id-%v", i), i, false)

		allMap.Store(objOne.InstanceId, objOne)
		all++
	}

	// 下面是纯随机的
	randNumNew := frNum(int64(randNum))
	for i := 0; i < randNumNew; i++ {

		objOne := MockEcsDescribeEcsOne("", "", i, false)
		// 随机
		allMap.Store(objOne.InstanceId, objOne)
		all++
	}
	return all
}

// 模拟数据
func MockEcsDescribeEcsOne(instanceName, instanceId string, index int, createByOrder bool) *models.ResourceEcs {
	rand.Seed(time.Now().UnixNano())

	if instanceId == "" {
		instanceId = uuid.New().String()
		instanceName = fmt.Sprintf("主机名-%v", instanceId[:10])
	}

	// 公共字段
	randVendor := []string{"阿里云", "华为云", "腾讯云", "aws", "世纪互联"}
	randEnv := []string{"dev", "press", "stage", "prod"}
	randVpcId := []string{"vpc-01", "vpc-02", "vpc-03"}
	randZoneId := []string{"beijing", "shanghai", "guangzhou", "tianjin", "shandong"}
	// 标签tags
	randTagKeys := []string{"arch", "idc", "os", "job", "env", "cluster", "type"}
	randTagValues := []string{"linux", "beijing", "windows", "shanghai", "arm64", "amd64", "darwin", "shijihulian"}

	// 独特字段
	randOSName := []string{"win10", "ubuntu", "centos", "Eurla", "debian"}
	randInstanceType := []string{"ecs.g8a.large", "ecs.g7se.large", "ecs.g7a-nps1.16xlarge", "ecs.g7ne.12small", "ecs.c8a.8xlarge", "ecs.c7se.6xlarge"}
	randCpu := []int{4, 8, 16, 32, 64, 128}
	randMemory := []int{8, 16, 32, 64, 128, 256, 512}
	randDisk := []int{100, 300, 500, 800, 1000, 2000, 5000}

	// 目的是4选1 返回0-3的数组
	// 比如8-15 15-8 +8
	frn := func(n int) int {
		rand.Seed(time.Now().UnixNano())
		return rand.Intn(n)
	}
	objOne := &models.ResourceEcs{}

	// 固定id
	objOne.InstanceId = instanceId
	objOne.InstanceName = instanceName

	// 公共字段
	objOne.Vendor = randVendor[frn(len(randVendor)-1)]
	objOne.ZoneId = randZoneId[frn(len(randZoneId)-1)]
	objOne.VpcId = randVpcId[frn(len(randVpcId)-1)]
	objOne.Env = randEnv[frn(len(randEnv)-1)]

	// tag
	tags := []string{}
	for _, tagKey := range randTagKeys {
		tagValue := randTagValues[frn(len(randTagValues)-1)]
		oneTag := fmt.Sprintf("%s=%s", tagKey, tagValue)
		tags = append(tags, oneTag)
	}
	objOne.Tags = tags
	// ip
	innerIp := genRandIpAddr()
	pubIp := genRandIpAddr()
	objOne.PrivateIpAddress = []string{innerIp}
	objOne.IpAddr = innerIp
	objOne.PublicIpAddress = []string{pubIp}

	// 独特的
	objOne.InstanceType = randInstanceType[frn(len(randInstanceType)-1)]
	objOne.OSName = randOSName[frn(len(randOSName)-1)]
	objOne.Cpu = randCpu[frn(len(randCpu)-1)]
	objOne.Memory = randMemory[frn(len(randMemory)-1)]
	objOne.Disk = randDisk[frn(len(randDisk)-1)]
	objOne.CreateByOrder = createByOrder

	objOne.Hash = objOne.GenHash()
	return objOne
}

// 模拟生成随机ip地址
func genRandIpAddr() string {
	rand.Seed(time.Now().Unix())
	ip := fmt.Sprintf("%d.%d.%d.%d", rand.Intn(255), rand.Intn(255), rand.Intn(255), rand.Intn(255))
	return ip
}

// ecs 转换的方法 ：阿里云的 同时算hash
func (cm *CronManager) ConvertEcsCloudAli(ins ecs.Instance) *models.ResourceEcs {
	return nil
}
